Repository navigation
iOS: liquid Today honours the Day-cycle background toggle (closes #16 parity gap) - #17
Merged
Merged
Conversation
Closes the last parity gap from #16. iOS`\s LiquidTodayView always drew the sky, ignoring the "Day-cycle background" setting — so turning it off did NOT give the plain dark canvas it does on Android and the classic TodayView. The "Sky behind cards" switch was therefore standalone on iOS vs day-cycle-gated on Android. - Gate the LiquidTodayView sky on @AppStorage(SceneBackgroundPrefs.enabledKey); off = the plain surfaceBase canvas (matches Android + classic Today). - Disable/grey the "Sky behind cards" Settings toggle when the day-cycle background is off (the sky it extends is not drawn then) — mirrors the Android `enabled = showDayCycleBackground`.
DX23876
referenced
this pull request
in DX23876/noop
Jul 22, 2026
iOS: liquid Today honours the Day-cycle background toggle (closes #16 parity gap)
ryanbr
added a commit
that referenced
this pull request
Sep 7, 2026
…on the link (#1950) Groundwork for #1949. Nothing is gated and no behaviour changes; this makes a 5/MG log answerable. ## What a capture could not tell you An MG log showed the four puffin notify chars discovered, one standard-HR subscribe, and nothing further: ``` [connection] service fd4b0001-cce1-4033-93ce-002d5875f58a (5 char) [connection] fd4b0003-... props=0x10 (Notify) [connection] fd4b0004-... props=0x10 (Notify) [connection] fd4b0005-... props=0x10 (Notify) [connection] fd4b0007-... props=0x10 (Notify) Subscribed 00002a37-0000-1000-8000-00805f9b34fb ``` Reading that, there is no way to tell the app declined to subscribe them from the strap refusing. Those are opposite answers for #1635: one is a setting, the other is the verdict the probe exists to obtain. Both silences are now spoken. ## The probe's skip reason `beginUnbondedOffloadProbe` returned silently whenever `shouldProbeUnbondedOffload` said no. It now names which of the seven conditions decided, in the gate's own order so the reason printed is the one that actually decided rather than the first that happens to be true. Once per link, because the path is retried. The common case is the actionable one: ``` unbonded probe skipped — the unbonded-offload experiment is off. The puffin notify chars stay unsubscribed on this link, so neither the historical offload nor a realtime IMU producer can reach us here. ``` The consequence clause is appended only for an unbonded 5/MG, since a bonded strap reaches those chars through the ordinary handshake and the sentence would be false there. ## The pairing dump `gattTreeLines` says what the strap OFFERS. This says what we HOLD with it: bond state, `didBond`, whether a hello went out, whether the probe is opted in, and per notify characteristic whether it carries a CCCD. It reports POSTURE, not outcome. A first draft of this carried a `subscribed` column and a re-review removed it, because it could not have been honest: the dump runs at service discovery, before the CCCD queue is drained, so every subscription is still ahead of it, and on API 33+ it could not have been read anyway since `writeDescriptor(descriptor, value)` never populates `descriptor.value`. It would have printed `subscribed=no` universally, including for a probe that HAD subscribed the puffin chars, which is the one case this exists to observe. Those outcomes are already in the log as one confirmed `Subscribed <uuid>` line each, so the note points a reader at them and says what their absence means. A test pins the column's absence with that reasoning attached. What that absence MEANS flips with the opt-in, and a later pass caught the note asserting one half of it unconditionally. With the probe off, only `HEART_RATE_CHAR` and `BATTERY_CHAR` reach `cccdQueue`, so an unsubscribed puffin char is ours. With the probe ON it subscribes those same chars itself, so a missing line there is the STRAP refusing, which is the opposite conclusion and the whole reason the probe is run. The note branches, and both branches are pinned with each asserting the other's wording is absent. On encryption it deliberately claims less than a reader might want. Android publishes no link-encryption flag to a GATT client, and a remote characteristic's permissions read back as 0, so bond state is a proxy and not the same question. The note names the two CCCD write statuses that do answer it, 5 and 15, which is what the probe already listens for. A test pins that wording so a later edit cannot quietly upgrade it to a claim the code cannot make. `bondStateName` is `BondStateTrace`'s, not a second spelling: two renderings of one OS state in one log is how a reader ends up believing they are two readings. ## Tests The explanation and the gate are pinned against each other EXHAUSTIVELY, over every combination of the seven inputs. They are separate `when` chains over the same conditions, and the failure mode is a condition added to one and forgotten in the other, which no hand-picked case is likely to sit on. Plus order, consequence-clause suppression, the two retirement kinds, and the dump's shape. The gate's inputs are read ONCE and the same values handed to the explanation. They are `@Volatile` fields and a live pref read, so re-reading them could name a reason that was not the one that decided. Android suite 5520 green, doc-comment gate clean. ## Scope Android only, and the twinned file already stated the rule. `GattCapability` has a Swift half, so an untwinned function in it deserves an answer, and its header gives one: `gattTreeLines` was twinned because CoreBluetooth exposes services, characteristics and properties, "unlike the bond-state and pairing helpers, which have no Apple equivalent at all". The pairing dump is one of those, keyed on an OS bond state CoreBluetooth does not publish and on an Android-only probe's opt-in. The skip reason has nothing to twin either: there is no iOS probe whose silence needs explaining. That rationale is now recorded at the function rather than left to be re-derived. Worth recording, because it runs opposite to the guess: iOS is the platform that COULD carry the subscription column removed above. `CBCharacteristic.isNotifying` is authoritative where Android's `descriptor.value` is null on API 33+, and iOS already re-subscribes the puffin chars post-bond precisely because the strap refuses them until the link is encrypted (issue #17). An iOS equivalent would be a different and slightly better instrument, emitted after the subscribe attempt. The two lines have deliberately different visibility. The domain argument to `log()` is a TAG, not a gate, so the skip reason lands in an ordinary strap log: one line, only on a no-hello 5/MG link, explaining a silence to whoever reads the log they already have. The dump is eight lines of readout and stays behind the Test Centre connection domain. Local reads on already-discovered state, gated to the Test Centre connection domain. No GATT operation and no traffic, which is the same discipline `gattTreeLines` was written to, and the reason both work on exactly the strap that no puffin probe can reach.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the one parity gap flagged in #16. On iOS,
LiquidTodayViewalways drew the sky, ignoring the "Day-cycle background" setting — so turning it off did not give the plain dark canvas it does on Android (and the classicTodayView). That also made the "Sky behind cards" switch standalone on iOS vs. day-cycle-gated on Android.Changes
@AppStorage(SceneBackgroundPrefs.enabledKey)— off = the plainsurfaceBasecanvas (parity with Android + classic Today).\t drawn then) — mirrors Android\senabled = showDayCycleBackground.Result — full parity
Default behaviour unchanged (day-cycle defaults ON). Swift compile-gated by CI.